/*
Mkup - DOM node builder

Written as Part of Ostium, a product of agimatec GmbH <http://www.agimatec.de>

License:

The MIT License

Copyright (c) 2009 Sebastian Schuth

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.*/

/**
 * Returns a Mkup object that can be used to create a DOM tree in a functional fashion.
 *
 * @param {Element} element The element to write to when calling write().
 *
 * @example
 * // Creating a simple list
 * var m = Mkup.mkup(myContainer);                                 
 * m.child("ul")
 *     .child("li").text("item 1")
 *     .element("li").text("item 2");
 * m.write();
 * // creates the same DOM nodes as the following markup:
 * // <ul>
 * //  <li>item 1</li>                                           
 * //  <li>item 2</li>
 * // </ul>
 *
 * @example
 * // Creating a simple list, a bit more elegant using child() call
 * var m = Mkup.mkup(myContainer);
 * m.child("ul").child()
 *     .element("li").text("item 1")
 *     .element("li").text("item 2");
 * m.write();
 * // creates the same DOM nodes as the following markup:
 * // <ul>
 * //  <li>item 1</li>
 * //  <li>item 2</li>
 * // </ul>
 *
 * @example
 * // Using up() to add a sibbling node
 * var m = Mkup.mkup(myContainer);
 * m.child("ul").child()
 *   .element("li").text("item 1")
 *   .element("li").text("item 2")
 *   .up("ol").child()
 *   .element("li").text("ordered")
 *   .element("li").text("ordered");
 * m.write();
 * // creates the same DOM nodes as the following markup:
 * // <ul>
 * //  <li>item 1</li>
 * //  <li>item 2</li>
 * // </ul>
 * // <ol>
 * //  <li>ordered</li>
 * //  <li>ordered</li>
 * // </ol>

 */
var Mkup={};
Mkup.mkup = function(element) {

    /**
     * @constructor
     *
     * @param {Element} elementToAppendTo   The element the created tree(s)
     *                                      will get appended to when write() gets called.
     */
    function Mkup(elementToAppendTo) {
        var root = document.createDocumentFragment(),
                node = root,
                elements = {},
                nextEleIsChild = false,
                that = this,
				tags = ["a", "abbr", "acronym", "address", "area", "b", "base", "basefont", "bdo", "big", "blockquote", "br", "button", "caption", "center", "cite", "code", "col", "colgroup", "dd", "del", "dfn", "dir", "div",
				"dl", "dt", "em", "fieldset", "font", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "label", "legend", "li", "link", "map", "menu", "meta",
				"object", "ol", "optgroup", "option", "p", "param", "pre", "q", "s", "samp", "script", "select", "small", "span", "strike", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead",
				"title", "tr", "tt", "u", "ul"];
		tags.each(function(tag){
			that[tag] = function(){
				return that.element.apply(that,[tag].concat($A(arguments)));
			};
			that["child"+tag.capitalize()] = function(){
				return that.child.apply(that,[tag].concat($A(arguments)));
			};
		});
        this.appendTo = elementToAppendTo;

        /**
         * @private
         * Creates a DOM node and stores it if it has a name.
         * @param {string|element} tag          The name of the tag that should get created or an element to add.
         * @param {object|string} [attributes]  The attributes that the new tag should have or the
         *                                      css class name it should get
         * @param {string} [name]               The name of the element to use when storing a
         *                                      reference to the element.
         */
        function createNode(tag, attributes, name) {
            var element = (tag.tagName) ? $(tag) : new Element(tag);
            if (attributes) {
                if (typeof attributes === "string") {
                    element.addClassName(attributes);
                } else {
                    element.writeAttribute(attributes);
                }
            }
            if (name) {
                if (elements[name]) throw new Error("Element name already in use. tag=" + tag + ", name=" + name);
                elements[name] = element;
            }
            return element;
        }

        /**
         * @private
         *
         * @param nodeToAppendTo
         * @param writeElementsToThis
         * @param {boolean} clone   If true, the documentFragment gets cloned before writing it into the DOM 
         *                          to preserve its contents.
         */
        function write(nodeToAppendTo, writeElementsToThis, clone) {
            var _elements = that.createdElements(writeElementsToThis);
            nodeToAppendTo.appendChild(clone ? root.cloneNode(true) : root);
            return _elements;

        }

        /**
         * Gets the references to the elements created so far
         * or - if a string is given as parameter - returns the element with the given name.
         *
         * @param {object|string} writeHereOrGetElementWithName Object to write elements to <b>or</b> the name os the
         *                                                      element that should be retreved
         * @return {array|Element} Array of elements or the element with the given name.
         *
         * @throws if the given parameter is a string and no element with the given name was found.
         */
        this.createdElements = function(writeHereOrGetElementWithName) {
            var _elements = elements, theElement;

            if (writeHereOrGetElementWithName) {
                if (typeof writeHereOrGetElementWithName === "string") {
                    theElement = elements[writeHereOrGetElementWithName];
                    if (!theElement) {
                        throw new Error("agimatec.util.mkup()#createdElements - unknown element name="
                                + writeHereOrGetElementWithName);
                    }
                    return elements[writeHereOrGetElementWithName];
                } else {
                    Object.extend(writeHereOrGetElementWithName, elements);
                }
            }
            return _elements;
        };

        /**
         * Creates a child node of the current level and moves the cursor onto the level of the created child.
         * 
         * @param {string} [tag]                    Name of the tag to create a node for.
         * @param {object|string} [attributes]      Attributes for the tag or a css class name for it.
         * @param {string} [name]                   Reference name for the new node.
         */
        this.child = function(tag, attributes, name) {
            if (arguments.length === 0) {
                nextEleIsChild = true;
                return this;
            }
            var element = createNode(tag, attributes, name);
            node.appendChild(element);
            node = element;
            return this;
        };

        /**
         * Creates a new node on the current level.
         *
         * @param {string} [tag]                    Name of the tag to create a node for.
         * @param {object|string} [attributes]      Attributes for the tag or a css class name for it.
         * @param {string} [name]                   Reference name for the new node.
         */
        this.element = function(tag, attributes, name) {
            if (nextEleIsChild || node === root) {
                nextEleIsChild = false;
                return this.child(tag, attributes, name);
            }
            var newNode = createNode(tag, attributes, name);
            node.parentNode.appendChild(newNode);
            node = newNode;
            return this;
        };

        /**
         * Moves the "cursor" one level up in the tree. The next calls to child() and
         * element() will get executed relavive to the parent node after a call to this function.
         *
         * Optionally, a tag can be created after moving up, creating a sibbling node to any nodes that exists
         * on the current level.
         * @param {string} [tag]                    Name of the tag to create a node for.
         * @param {object|string} [attributes]      Attributes for the tag or a css class name for it.
         * @param {string} [name]                   Reference name for the new node.
         */
        this.up = function(tag, attributes, name) {
            var parent = node.parentNode;
            if (!parent) throw new Error("requested up() is useless - already root node");
            node = parent;
            if (tag) {
                return this.element(tag, attributes, name);
            }
            return this;
        };
		
		/**
		 * Moves the "cursor" to the level of the node with the given name: the next 
		 * call to element() will create a sister node of the node with the given name.
		 */
		this.upTo = function(refName){
			var referenced = elements[refName];
			if(!referenced){
				throw new Error("reference '"+refName+"' for upTo () not found!");
			}
			node = referenced;
			return this;
		};
		
		/**
		 * Puts the cursor back to the top level.
		 */
		this.upToTop = function(){
			node = root;
			return this;
		};

        /**
         * Adds a text node.
         * Note: Does not change where new child/element calls add created elements!
         */
        this.text = function(text) {
            node.appendChild(document.createTextNode(text));
            return this;
        };

        /**
         * Sets the given text as innerHTML instead of a text node (as text() does)
         * @param {string} html The html to set as inner html for the node.
         */
        this.markup = function(html) {
            node.innerHTML = html;
            return this;
        };

        /**
         * Returns the root element created (a DocumentFragment instance)
         */
        this.root = function() {
            return root;
        };

        /**
         * Stops the current DOM building process, removing everything created so far.
         */
        this.reset = function() {
            root = document.createDocumentFragment();
            node = root;
            elements = {};
            nextEleIsChild = false;
            return this;
        };

        /**
         * Ends the markup building, appending everything created to the
         * first element matched by the element used to start the build (mkup());
         *
         * @param {object} [writeElementsToThis]    If set, the references to the created elements
         *                                          get written into this object.
         *
         * @return An object containing the named nodes that were written to the DOM.
         */
        this.write = function(writeElementsToThis) {
            var _elements;
            _elements = write(this.appendTo, writeElementsToThis, false);
            this.reset();
            return _elements;
        };

        /**
         * Writes a copy of the created nodes into the given element.
         *
         * @param {Element} nodeToAppendTo          The node to append the created DOM structure to.
         * @param {object} [writeElementsToThis]      Object used to store the element references to.
         *
         * @return An object containing the named nodes that were written to the DOM.
         */
        this.writeCopy = function(nodeToAppendTo, writeElementsToThis) {
            return write(nodeToAppendTo, writeElementsToThis, true);
        };

    }
    return new Mkup(element);
};